#include <string>
#include <mutex>
#include <thread>

#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <unistd.h>
#include <sys/time.h>

#include "rtc_base/message_queue.h"
#include "rtc_base/physical_socket_server.h"
#include "rtc_base/ref_counted_object.h"
#include "rtc_base/ssl_adapter.h"
#include "rtc_base/thread.h"
#include "rtc_base/logging.h"
#include "rtc_base/strings/json.h"

#include "api/peer_connection_interface.h"
#include "p2p/base/stun_server.h"
#include "third_party/libyuv/include/libyuv.h"
#include "api/video/i420_buffer.h"
#include "common_video/libyuv/include/webrtc_libyuv.h"

//#define STB_IMAGE_IMPLEMENTATION
//#include "stb_image.h"

#include "easywsclient.hpp"
#include "conductor.h"
#include "webrtc_client.h"

bool stopedWebrtc = false;
std::string userNameInternalWebrtc;
std::time_t send_ping_time = 0;
std::time_t receive_pong_time;
bool websocket_closed = false;

static easywsclient::WebSocket::pointer websocket_client_webrtc = NULL;
std::mutex websocket_lock;
std::mutex message_lock;
std::vector<std::string> message_queue; 
bool loginInSuccessWebrtc = false;
rtc::scoped_refptr<Conductor> conductor(new rtc::RefCountedObject<Conductor>());

void websocketSendMessage(const std::string& message) {
    std::lock_guard<std::mutex> lock_ws(websocket_lock, std::adopt_lock);
    if(websocket_client_webrtc == NULL) {
        RTC_LOG(INFO) << "WebSocket closed, can not send message";
        return;
    }
    easywsclient::WebSocket::readyStateValues webSocketState = websocket_client_webrtc->getReadyState();
    if(webSocketState == easywsclient::WebSocket::OPEN) {
        websocket_client_webrtc->send(message.c_str());
    } else {
        RTC_LOG(INFO) << "WebSocket not opened, can not send message";
    }
}

void handleLogin(Json::Value jmessage) {
    bool login_success = false;
    rtc::GetBoolFromJson(jmessage["success"], &login_success);
    if(!loginInSuccessWebrtc) {
        if(login_success){
            loginInSuccessWebrtc = true;
        } else {
            fprintf(stderr, "handleLogin: device name invaild\n");
            srand((unsigned)time(NULL));
            //userNameInternalWebrtc = userNameInternalWebrtc + "-" + std::to_string(rand());
            fprintf(stdout, "handleLogin: create new device name %s\n", userNameInternalWebrtc.c_str());
            Json::Value payload_type("login");
            Json::Value payload_name(userNameInternalWebrtc);
            Json::Value payload;
            payload["type"] = payload_type;
            payload["name"] = payload_name;
            std::string payload_str = rtc::JsonValueToString(payload);
            websocketSendMessage(payload_str);
        }
    } else {
        fprintf(stdout, "handleLogin: loginInSuccessWebrtc already\n");
    }
}

int handle_message() {
    int message_num;
    std::string message;
    {
        std::lock_guard<std::mutex> lock_message(message_lock, std::adopt_lock);
        if(message_queue.size() == 0) return 0;
        message = message_queue.back();
        message_queue.pop_back();
        message_num = message_queue.size();
    }
    //RTC_LOG(INFO) << __FUNCTION__ << std::this_thread::get_id();
    Json::CharReaderBuilder reader;
    std::string json_errs;
    Json::Value jmessage;
    std::istringstream message_stream(message);
    if (!Json::parseFromStream(reader, message_stream, &jmessage, &json_errs)) {
        return message_num;
    }
    std::string message_type;
    rtc::GetStringFromJson(jmessage["type"], &message_type);
    std::string peerid;
    rtc::GetStringFromJson(jmessage["from"], &peerid);

    if(message_type != "pong-signal") RTC_LOG(INFO) << __FUNCTION__ << " got message: " << message;
    if(message_type == "message") {
        std::string message_payload = rtc::JsonValueToString(jmessage["payload"]);
        std::string message_payload_str = "";
        for(std::string::const_iterator it = message_payload.begin(); it != message_payload.end(); ++it) {
            if (*it != '"') {
                message_payload_str += *it;
            }
        }
    } else if(message_type == "save-data-over") {
    } else if(message_type == "login") {
        handleLogin(jmessage);
    } else if(message_type == "file-transform-parameter-accept") {
    } else if(message_type == "file-transform-cancel") {
    } else if(message_type == "file-transform") {
    } else if(message_type == "offer-loopback") {
        if("" != conductor->peerid) {
            Json::Value payload;
            Json::Value payload_type("peer-busying");
            payload["type"] = payload_type;
            Json::Value payload_name(peerid);
            payload["name"] = payload_name;
            std::string payload_str = rtc::JsonValueToString(payload);
            websocketSendMessage(payload_str);
            return message_num;
        }
        conductor->createOffer(peerid);
    } else if(message_type == "offer") {
        //conductor->handleOffer(jmessage["payload"]);
    } else if(message_type == "answer") {
        if(peerid != conductor->peerid) return message_num;
        else conductor->setAnswer(jmessage["payload"]);
    } else if(message_type == "candidate") {
        if(peerid != conductor->peerid) return message_num;
        conductor->addIceCandidate(jmessage["payload"]);
        Json::Value iceCandidateList = conductor->getIceCandidateList();
        if(iceCandidateList.size() > 0){
            std::string candidate_str = rtc::JsonValueToString(iceCandidateList);
            if(conductor->sendCadateString != candidate_str){
                conductor->sendCadateString = candidate_str;
                Json::Value candidate_json;
                candidate_json["payload"] = iceCandidateList;
                Json::Value peer_name(peerid);
                candidate_json["name"] = peer_name;
                Json::Value type_local("candidate_list");
                candidate_json["type"] = type_local;
                std::string payload_str = rtc::JsonValueToString(candidate_json);
                websocketSendMessage(payload_str);
            }
        }
    } else if(message_type == "leave") {
        if(peerid != conductor->peerid) return message_num;
        conductor->hangUp();
    } else if(message_type == "server_kick_out") {
        conductor->hangUp();
    } else if(message_type == "pong-signal") {
        receive_pong_time = std::time(nullptr);
        if("" != conductor->peerid) {
            Json::Value query_json;
            Json::Value peer_name(conductor->peerid);
            query_json["name"] = peer_name;
            Json::Value type_local("query_robot");
            query_json["type"] = type_local;
            std::string payload_str = rtc::JsonValueToString(query_json);
            websocketSendMessage(payload_str);
        }
    } else if(message_type == "query_robot") {
        std::string current_peer;
        static int no_peer_times = 0;
        rtc::GetStringFromJson(jmessage["peer"], &current_peer);
        if(current_peer != userNameInternalWebrtc){
            if(no_peer_times >= 2) {
                conductor->hangUp();
                RTC_LOG(INFO) << __FUNCTION__ << " query_robot peer not online, shutdown connection " << message;;
                no_peer_times = 0;
            } else {
                no_peer_times += 1;
            }
        }
    } else if(message_type == "peer-not-exist") {
        conductor->hangUp();
    } else {
        RTC_LOG(INFO) << __FUNCTION__ << " got unknow message: " << message;;
    }
    return message_num;
}

class CustomSocketServer : public rtc::PhysicalSocketServer {
public:
    explicit CustomSocketServer() {}
    virtual ~CustomSocketServer() {}

    void SetMessageQueue(rtc::MessageQueue* queue) override {
        message_queue_ = queue;
    }

    bool poll_message(){
        if(websocket_closed){
            std::lock_guard<std::mutex> lock_message(message_lock, std::adopt_lock);
            if(conductor->peerid != ""){
                std::string message = "{\"type\":\"leave\", \"payload\":\"\", \"from\":\"" + conductor->peerid + "\"}";
                message_queue.insert(message_queue.begin(), message);
            }
            websocket_closed = false;
        }
        if(conductor->need_hangeup) {
            conductor->need_hangeup = false;
            conductor->hangUp();
        }
        //int message_num;
        handle_message();
        /*
        if(message_num > 0){
            message_num = handle_message();
        }
        */
        std::this_thread::sleep_for(std::chrono::microseconds(10 * 1000));
        return true;
    }

    // Override so that we can also pump the GTK message loop.
    bool Wait(int cms, bool process_io) override {
        poll_message();
        if(false) {
            message_queue_->Quit();
        }
        return rtc::PhysicalSocketServer::Wait(0 /*cms == -1 ? 1 : cms*/, process_io);
    }
protected:
    rtc::MessageQueue* message_queue_;
};

void websocket_message(const std::string& message) {
    //RTC_LOG(INFO) << __FUNCTION__ << " got message: " << message;
    std::lock_guard<std::mutex> lock_message(message_lock, std::adopt_lock);
    message_queue.insert(message_queue.begin(), message);
}

void websocket_send_ping() {
    if(std::time(nullptr) - send_ping_time < 10) return;
    Json::Value payload_type("ping-signal");
    Json::Value payload;
    payload["type"] = payload_type;
    std::string payload_str = rtc::JsonValueToString(payload);
    websocketSendMessage(payload_str);
    send_ping_time = std::time(nullptr);
}

void websocket_send_login() {
    Json::Value payload_type("login");
    Json::Value payload_name(userNameInternalWebrtc);
    Json::Value payload;
    payload["type"] = payload_type;
    payload["name"] = payload_name;
    std::string payload_str = rtc::JsonValueToString(payload);
    websocketSendMessage(payload_str);
}

void *init_websocket(void* args){
    receive_pong_time = std::time(nullptr);
    std::string websocket_host = "ws://test.iimiim.cn:3000";
    websocket_client_webrtc = easywsclient::WebSocket::from_url(websocket_host);
    if(websocket_client_webrtc) {
        RTC_LOG(INFO) << "WebSocket created success";
    } else {
        RTC_LOG(LERROR) << "WebSocket create failed";
        while(!websocket_client_webrtc){
            websocket_client_webrtc = easywsclient::WebSocket::from_url(websocket_host);
            if(websocket_client_webrtc) {
                RTC_LOG(INFO) << "WebSocket created success";
            } else {
                RTC_LOG(LERROR) << "WebSocket create failed";
            }
            std::this_thread::sleep_for(std::chrono::microseconds(1000 * 1000));
        }
    }
    websocket_send_login();

    easywsclient::WebSocket::readyStateValues webSocketState = websocket_client_webrtc->getReadyState();
    while (webSocketState == easywsclient::WebSocket::OPEN) {
        if(stopedWebrtc) break;
        {
            std::lock_guard<std::mutex> lock_ws(websocket_lock, std::adopt_lock);
            websocket_client_webrtc->poll();
            websocket_client_webrtc->dispatch(websocket_message);
        }
        websocket_send_ping();

        std::this_thread::sleep_for(std::chrono::microseconds(100 * 1000));
        {
            std::lock_guard<std::mutex> lock_ws(websocket_lock, std::adopt_lock);
            webSocketState = websocket_client_webrtc->getReadyState();
            if(webSocketState != easywsclient::WebSocket::OPEN || std::time(nullptr) - receive_pong_time > 30) {
                RTC_LOG(LERROR) << "webSocketState != easywsclient::WebSocket::OPEN! " << webSocketState;
                websocket_closed = true;
                std::this_thread::sleep_for(std::chrono::microseconds(3000 * 1000)); // waiting for conductor hangUp
                receive_pong_time = std::time(nullptr);
                loginInSuccessWebrtc = false;
                RTC_LOG(LERROR) << "webSocketState != easywsclient::WebSocket::OPEN";
                delete websocket_client_webrtc;
                websocket_client_webrtc = NULL;
                while(!websocket_client_webrtc){
                    websocket_client_webrtc = easywsclient::WebSocket::from_url(websocket_host);
                    if(websocket_client_webrtc) {
                        RTC_LOG(INFO) << "WebSocket created success";
                        websocket_send_login();
                        websocket_send_ping();
                    } else {
                        RTC_LOG(LERROR) << "WebSocket create failed";
                    }
                    std::this_thread::sleep_for(std::chrono::microseconds(1000 * 1000));
                }
                webSocketState = websocket_client_webrtc->getReadyState();
            }
        }
    }
    RTC_LOG(LERROR) << "webSocket thread exit";
    return NULL;
}

void rgb2I420(uint8_t *dst, uint8_t *rgb, int width, int height) {
    size_t image_size = width * height;
    size_t upos = image_size;
    size_t vpos = upos + image_size / 4;
    size_t i = 0;

    for(int line = 0; line < height; ++line ) {
        if( !(line % 2) ) {
            for(int x = 0; x < width; x += 2 ) {
                uint8_t r = rgb[3*i];
                uint8_t g = rgb[3*i + 1];
                uint8_t b = rgb[3*i + 2];
                dst[i++] = ((66*r + 129*g + 25*b) >> 8) + 16;
                dst[upos++] = ((-38*r + -74*g + 112*b) >> 8) + 128;
                dst[vpos++] = ((112*r + -94*g + -18*b) >> 8) + 128;

                r = rgb[3*i];
                g = rgb[3*i + 1];
                b = rgb[3*i + 2];
                dst[i++] = ((66*r + 129*g + 25*b) >> 8) + 16;
            }
        } else {
            for(int x = 0; x < width; x += 1 ) {
                uint8_t r = rgb[3*i];
                uint8_t g = rgb[3*i + 1];
                uint8_t b = rgb[3*i + 2];
                dst[i++] = ((66*r + 129*g + 25*b) >> 8) + 16;
            }
        }
    }

}

webrtc::VideoFrame get_video_frame(unsigned char *img_data, int w, int h){
    unsigned char *yvv_data = (unsigned char*)malloc(w*h*3/2 * sizeof(unsigned char));
    //RTC_LOG(INFO) << "\n\nget_video_frame \n\n" << w << " " << h << " " << w*h*3/2 * sizeof(unsigned char);
    rgb2I420(yvv_data, img_data, w, h);

    rtc::scoped_refptr<webrtc::I420Buffer> buffer = webrtc::I420Buffer::Create(w, h, w, (w+1) / 2, (w+1) / 2);
    const int conversionResult = libyuv::ConvertToI420(
        yvv_data, w*h*3/2*sizeof(unsigned char),
        buffer.get()->MutableDataY(), buffer.get()->StrideY(),
        buffer.get()->MutableDataU(), buffer.get()->StrideU(),
        buffer.get()->MutableDataV(), buffer.get()->StrideV(),
        0, 0,
        w, h, w, h, libyuv::kRotate0, libyuv::FOURCC_I420);
    if (conversionResult < 0) {
        RTC_LOG(LS_ERROR) << "Failed to convert capture frame from type " << "to I420.";
    }
    webrtc::VideoFrame captureFrame =
        webrtc::VideoFrame::Builder()
        .set_video_frame_buffer(buffer)
        .set_timestamp_rtp(0)
        .set_timestamp_ms(rtc::TimeMillis())
        .set_rotation(webrtc::kVideoRotation_0)
        .build();
    free(yvv_data);
    return captureFrame;
}

/*
unsigned char * load_image_stb(char *filename, int *w, int *h, int *c) {
    unsigned char *data = stbi_load(filename, w, h, c, 3);
    if (!data) {
        fprintf(stderr, "Cannot load image \"%s\"\nSTB Reason: %s\n", filename, stbi_failure_reason());
        exit(0);
    }
    return data;
}

void *send_image(void* args){
    int w, h, c;
    unsigned char *img_data1 = load_image_stb("/home/luyao/3.jpg", &w, &h, &c);
    webrtc::VideoFrame frame1 = get_video_frame(img_data1, w, h);
    unsigned char *img_data2 = load_image_stb("/home/luyao/2.jpg", &w, &h, &c);
    webrtc::VideoFrame frame2 = get_video_frame(img_data2, w, h);
    free(img_data1);
    free(img_data2);
    int index = 1;

    while(true){
        if(nullptr != conductor->videoSource){
            if(index % 2 == 0){
                frame1.set_ntp_time_ms(webrtc::Clock::GetRealTimeClock()->CurrentNtpInMilliseconds());
                conductor->fake_capturer->OnFrame(frame1);
            } else {
                frame2.set_ntp_time_ms(webrtc::Clock::GetRealTimeClock()->CurrentNtpInMilliseconds());
                conductor->fake_capturer->OnFrame(frame2);
            }
            index += 1;
        }
        std::this_thread::sleep_for(std::chrono::microseconds(100 * 1000));
    }
}
*/

std::string getMacAddress()
{
    int fd;
    struct ifreq ifr;
    char iface[256] = "eth0";

    fd = socket(AF_INET, SOCK_STREAM, 0);
    //ifr.ifr_addr.sa_family = AF_INET;
    strncpy((char *)ifr.ifr_name , iface , IFNAMSIZ-1);
    ioctl(fd, SIOCGIFHWADDR, &ifr);
    close(fd);
    unsigned char *mac = (unsigned char *)ifr.ifr_hwaddr.sa_data;
    char mac_local[512];
    sprintf(mac_local, "%.2x%.2x%.2x%.2x%.2x%.2x" , mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
    return std::string(mac_local);
}

std::string get_device_id(){
    const char *boot_deviceid = "/boot/deviceid";
    char device_str[512] = {0};
    if(0 == access(boot_deviceid, 0)){
        FILE *fp = fopen(boot_deviceid, "r");
        if(fp != NULL) {
            if(fgets(device_str, 512, fp) != NULL){
                fclose(fp);
                std::string device_name = std::string(device_str);
                if(device_name.length() > 6){
                    return std::string(device_str);
                } else {
                    fprintf(stderr, "get_device_id device SN failed\n");
                }
            }
        } else {
            fprintf(stderr, "get_device_id open %s file failed\n", boot_deviceid);
        }
    }
    return getMacAddress();
}

void *webrtc_thread(void* args){
    int logLevel = rtc::INFO;
    //int logLevel = rtc::WARNING;
    //int logLevel = rtc::LS_ERROR;
    rtc::LogMessage::LogToDebug((rtc::LoggingSeverity)logLevel);
    rtc::LogMessage::LogTimestamps();
    rtc::LogMessage::LogThreads();
    RTC_LOG(INFO) << "Logger level:" <<  rtc::LogMessage::GetLogToDebug() << " user id " << userNameInternalWebrtc;

    CustomSocketServer socket_server;
    rtc::AutoSocketServerThread thread(&socket_server);
    rtc::InitializeSSL();
    thread.Run();
    rtc::CleanupSSL();
    RTC_LOG(LS_ERROR) << "webrtc_thread quit!";
    return 0;
}

void start_webrtc_video(){
    userNameInternalWebrtc = get_device_id();
    //userNameInternalWebrtc = "test001";

    pthread_t pthread_id;
    pthread_create(&pthread_id, NULL, init_websocket, NULL);
    //pthread_t send_image_id;
    //pthread_create(&send_image_id, NULL, send_image, NULL);
    pthread_t webrtc_id;
    pthread_create(&webrtc_id, NULL, webrtc_thread, NULL);
}

int get_webrtc_status(){
    if(nullptr != conductor->videoSource) return 1;
    else return 0;
}

double getMsecOfNow()
{
    struct timeval tim;
    gettimeofday(&tim, NULL);
    return 1000.0 * tim.tv_sec + (tim.tv_usec / 1000.0);
}

void send_webrtc_image(unsigned char *img_data, int w, int h){
    static double send_image_time;
    double time_now = getMsecOfNow();
    if(nullptr != conductor->videoSource && time_now - send_image_time > 100){
        send_image_time = time_now;
        webrtc::VideoFrame frame = get_video_frame(img_data, w, h);
        frame.set_ntp_time_ms(webrtc::Clock::GetRealTimeClock()->CurrentNtpInMilliseconds());
        conductor->fake_capturer->OnFrame(frame);
    }
}

void stop_webrtc_video(){
    stopedWebrtc = true;
    websocket_closed = true;
}

int main_sdf(int argc, char **argv){
    userNameInternalWebrtc = get_device_id();
    int logLevel = rtc::INFO;
    //int logLevel = rtc::WARNING;
    rtc::LogMessage::LogToDebug((rtc::LoggingSeverity)logLevel);
    rtc::LogMessage::LogTimestamps();
    rtc::LogMessage::LogThreads();
    RTC_LOG(INFO) << "Logger level:" <<  rtc::LogMessage::GetLogToDebug() << " user id " << userNameInternalWebrtc;

    pthread_t pthread_id;
    pthread_create(&pthread_id, NULL, init_websocket, NULL);
    //pthread_t send_image_id;
    //pthread_create(&send_image_id, NULL, send_image, NULL);

    CustomSocketServer socket_server;
    rtc::AutoSocketServerThread thread(&socket_server);
    rtc::InitializeSSL();
    thread.Run();
    rtc::CleanupSSL();
    return 0;
}
